package com.mongo.util;

import com.mongo.func.SFunction;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;

import java.beans.Introspector;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 反射工具类
 * @author xiazhun
 * @create 2022/6/8
 */
public class ReflectionUtil {
    private static final Map<SFunction<?, ?>, Field> cache = new ConcurrentHashMap<>();
    public static <T, R> String getFieldName(SFunction<T, R> function) {
        Field field = ReflectionUtil.getField(function);
        return field.getName();
    }
    public static Field getField(SFunction<?, ?> function) {
        return cache.computeIfAbsent(function, ReflectionUtil::findField);
    }

    public static Object getValue(Object origin, Field field){
        Assert.notNull(origin);
        Assert.notNull(field);
        try {
            field.setAccessible(true);
            return field.get(origin);
        }catch (Exception e){
            return null;
        }
    }

    public static void setValue(Object origin, Field field, Object value){
        Assert.notNull(origin);
        Assert.notNull(field);
        try {
            field.setAccessible(true);
            field.set(origin,value);
        }catch (Exception ignore){
        }

    }
    public static Field findField(SFunction<?, ?> function) {
        Field field = null;
        String fieldName = null;
        try {
            // 第1步获取SerializedLambda
            Method method = function.getClass().getDeclaredMethod("writeReplace");
            method.setAccessible(Boolean.TRUE);
            SerializedLambda serializedLambda = (SerializedLambda) method.invoke(function);
            // 第2步 implMethodName 即为Field对应的Getter⽅法名
            String implMethodName = serializedLambda.getImplMethodName();
            if (implMethodName.startsWith("get") && implMethodName.length() > 3) {
                fieldName = Introspector.decapitalize(implMethodName.substring(3));
            } else if (implMethodName.startsWith("is") && implMethodName.length() > 2) {
                fieldName = Introspector.decapitalize(implMethodName.substring(2));
            } else if (implMethodName.startsWith("lambda$")) {
                throw new IllegalArgumentException("SerializableFunction不能传递lambda表达式,只能使⽤⽅法引⽤");
            } else {
                throw new IllegalArgumentException(implMethodName + "不是Getter⽅法引⽤");
            }
            // 第3步获取的Class是字符串，并且包名是“/”分割，需要替换成“.”，才能获取到对应的Class对象
            String declaredClass = serializedLambda.getImplClass().replace("/", ".");
            Class<?> aClass = Class.forName(declaredClass, false, ClassUtils.getDefaultClassLoader());
            // 第4步 Spring 中的反射⼯具类获取Class中定义的Field
            field = ReflectionUtils.findField(aClass, fieldName);
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 第5步如果没有找到对应的字段应该抛出异常
        if (field != null) {
            return field;
        }
        throw new NoSuchFieldError(fieldName);
    }
    /**
     * 循环向上转型, 获取对象的 DeclaredMethod
     * @param object : 子类对象
     * @param methodName : 父类中的方法名
     * @param parameterTypes : 父类中的方法参数类型
     * @return 父类中的方法对象
     */
    public static Method getDeclaredMethod(Object object, String methodName, Class<?> ... parameterTypes){
        Method method = null ;

        for(Class<?> clazz = object.getClass() ; clazz != Object.class ; clazz = clazz.getSuperclass()) {
            try {
                method = clazz.getDeclaredMethod(methodName, parameterTypes) ;
                return method ;
            } catch (Exception e) {
                //这里甚么都不要做！并且这里的异常必须这样写，不能抛出去。
                //如果这里的异常打印或者往外抛，则就不会执行clazz = clazz.getSuperclass(),最后就不会进入到父类中了

            }
        }

        return null;
    }
}
